<template>
  <div class="processDrawBody">
    <div class="containerBox">
      <el-button type="success"
                 size="small"
                 @click="handleZoom(0.2)">放大
      </el-button>
      <el-button type="warning"
                 size="small"
                 @click="handleZoom(-0.2)">缩小
      </el-button>
      <el-tooltip effect="light" placement="bottom">
        <template #content>
          <div style="color: #409eff">
            <el-button link title="下载为XML文件" @click="downloadProcessAsXml()">
              下载为XML
            </el-button>
            <br/>
            <el-button link title="下载为SVG文件" @click="downloadProcessAsSvg()">
              下载为SVG
            </el-button>
            <br/>
            <el-button link title="下载为BPMN文件" @click="downloadProcessAsBpmn()">
              下载为BPMN
            </el-button>
          </div>
        </template>
        <el-button title="下载流程">
          下载流程
        </el-button>
      </el-tooltip>
      <div ref="canvasRef" class="canvas"></div>
    </div>
  </div>
</template>

<script setup lang="ts">
import {ref, toRaw, onMounted} from 'vue';
import BpmnViewer from "bpmn-js/lib/Viewer";
import ModelingModule from 'bpmn-js/lib/features/modeling'
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas'
import {activeNodeKey, modelSource, modelSourceByProcessKey} from "@/core/api/service";
import {
  append as svgAppend,
  attr as svgAttr,
  create as svgCreate
} from 'tiny-svg'
import {Resume} from "@/core/entity/Resume";

const {category, processKey, processId, resumes} = defineProps(['category', 'processKey', 'processId', 'resumes']);
const emits = defineEmits(["click-element"]);

const canvasRef = ref();

let bpmnViewer: BpmnViewer;
const scale = ref(1);
const setNodeColor = (nodeCodes: any, colorClass: string, canvas: any) => {
  for (let i = 0; i < nodeCodes.length; i++) {
    canvas.addMarker(nodeCodes[i], colorClass);
  }
};
const handleZoom = (flag: number) => {
  // 放大缩小倍数flag可以自行设置
  if (flag < 0 && scale.value <= 0.3) {
    return;
  }
  scale.value += flag;
  bpmnViewer.get<any>("canvas").zoom(scale.value);
};

const overlays = ref<any>()
const clickElement = ref<any>()

const bindEvents = () => {
  let eventBus = bpmnViewer.get<any>('eventBus');
  overlays.value = bpmnViewer.get<any>('overlays');
  eventBus.on('element.click', (e: any) => {
    let element = e ? e.element : null
    if (clickElement.value?.id === element?.id) {
      clickElement.value = null;
    } else {
      toRaw(overlays.value).clear();
      clickElement.value = element;
      elementHover(element)
    }
    emits("click-element", element, e);
  });
  eventBus.on('element.hover', (e: any) => {
    let element = e ? e.element : null
    if (clickElement.value?.id === element?.id) {
      return;
    }
    elementHover(element)
  });
  eventBus.on('element.out', (e: any) => {
    let element = e ? e.element : null
    if (clickElement.value?.id !== element?.id) {
      toRaw(overlays.value).remove({element})
    }
  });
}

// 流程图的元素被 hover
const elementHover = (element: any) => {
  if (!element || !element.businessObject) {
    return;
  }
  if (!element.businessObject.name) {
    return;
  }
  if (element.businessObject.$type === 'bpmn:Process') {
    return;
  }

  let overlaysClass = 'element-overlays'
  let tasks: Resume[] = resumes?.filter((r: Resume) => r.processNodeKey === element.businessObject.id)
  let taskInfo = '';
  if (tasks && tasks.length > 0) {
    overlaysClass = 'element-process-overlays'
    for (let i = tasks.length - 1; i >= 0; i--) {
      const task = tasks[i];
      taskInfo += `<hr/>
                <table>
                    <tr>
                        <td>处置序号: ${i + 1} </td>
                        <td>履历序号: ${task.seq} </td>
                        <td>处置时间: ${task.handleDate}</td>
                        <td>处置耗时: ${task.timeShow}</td>
                    </tr>
                    <tr>
                        <td colspan="4" class="task_info_${task.processTypeStatus}">
                            【${task.processTypeName}】: ${task.handleInfo}
                        </td>
                    </tr>
                </table>`;
    }
  }

  let attrInfo = '';
  if (element.businessObject.$attrs) {
    for (let fieldName in element.businessObject.$attrs) {
      attrInfo += `<p>${fieldName}: ${element.businessObject.$attrs[fieldName]}</p>`
    }
  }
  let conditionExpression = '';
  if (element.businessObject.conditionExpression?.body) {
    conditionExpression = 'conditionExpression: ' + element.businessObject.conditionExpression?.body;
  }
  let html = `<div>
            <p>Elemet id: ${element.businessObject.id}</p>
            <p>Elemet name: ${element.businessObject.name || ''}</p>
            ${attrInfo}
            ${conditionExpression}
            ${taskInfo}
          </div>` // 默认值
  toRaw(overlays.value)?.add(element, {
    position: {left: 0, bottom: 0},
    html: `<div class="${overlaysClass}">${html}</div>`
  })
}

// 下载流程图到本地
const downloadProcess = async (type: string) => {
  try {
    // 按需要类型创建文件并下载
    if (type === 'xml' || type === 'bpmn') {
      const {error, xml} = await bpmnViewer.saveXML()
      if (error) {
        console.error(`[Process Designer Warn ]: ${error.message || error}`)
      }
      if (xml === undefined) {
        return;
      }
      let {href, filename} = setEncoded(type.toUpperCase(), xml)
      downloadFunc(href, filename)
    } else {
      const {svg} = await bpmnViewer.saveSVG()
      let {href, filename} = setEncoded('SVG', svg)
      downloadFunc(href, filename)
    }
  } catch (e: any) {
    console.error(`[Process Designer Warn ]: ${e.message || e}`)
  }

  // 文件下载方法
  function downloadFunc(href: string, filename: string) {
    if (href && filename) {
      let a = document.createElement('a')
      a.download = filename //指定下载的文件名
      a.href = href //  URL对象
      a.click() // 模拟点击
      URL.revokeObjectURL(a.href) // 释放URL 对象
    }
  }
}

// 根据所需类型进行转码并返回下载地址
const setEncoded = (type: string, data: string | number | boolean) => {
  const filename = 'diagram'
  const encodedData = encodeURIComponent(data)
  return {
    filename: `${filename}.${type}`,
    href: `data:application/${
        type === 'svg' ? 'text/xml' : 'bpmn20-xml'
    };charset=UTF-8,${encodedData}`,
    data: data
  }
}

const downloadProcessAsXml = () => {
  downloadProcess('xml')
}
const downloadProcessAsBpmn = () => {
  downloadProcess('bpmn')
}
const downloadProcessAsSvg = () => {
  downloadProcess('svg')
}

onMounted(() => {

  if (bpmnViewer) {
    bpmnViewer.destroy();
  }

  bpmnViewer = new BpmnViewer({
    container: canvasRef.value,
    additionalModules: [
      ModelingModule,
      MoveCanvasModule
    ]
  });

  const appendSvgMarkerEnd = () => {
    const marker = svgCreate('marker')
    svgAttr(marker, {
      id: 'sequenceflow-arrow-normal',
      viewBox: '0 0 20 20',
      refX: '11',
      refY: '10',
      markerWidth: '10',
      markerHeight: '10',
      orient: 'auto'
    })
    const path = svgCreate('path')
    svgAttr(path, {
      d: 'M 1 5 L 11 10 L 1 15 Z',
      style:
          ' stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1; fill: #79bbff !important'
    })

    const defs: Element = document.querySelector<any>('defs')
    svgAppend(marker, path)
    svgAppend(defs, marker)
  }

  if (processId) {
    modelSource(category, processId).then(res => {
      bpmnViewer.importXML(res).then(e => {
        appendSvgMarkerEnd();
        let canvas = bpmnViewer.get<any>("canvas");
        canvas.zoom("fit-viewport", "auto");
        activeNodeKey(category, processId).then(data => {
          setNodeColor(data.activeNodeKeys, "nodeSuccess", canvas);
          setNodeColor(data.currentNodeKeys, "nodeCurrent", canvas);
        });
        bindEvents();
      })
    });
  } else if (processKey) {
    modelSourceByProcessKey(category, processKey).then(res => {
      bpmnViewer.importXML(res).then(e => {
        appendSvgMarkerEnd();
        let canvas = bpmnViewer.get<any>("canvas");
        canvas.zoom("fit-viewport", "auto");
        bindEvents();
      })
    });
  }
})


</script>

<script lang="ts">
export default {
  name: "SillyBpmnView",
}
</script>

<style lang="scss">
@import 'bpmn-js/dist/assets/diagram-js.css';
// 左边工具栏以及编辑节点的样式
@import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
@import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
@import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
@import 'bpmn-js-properties-panel/dist/assets/properties-panel.css';

.processDrawBody {
  text-align: left;
}

.containerBox {
  height: 100%;
  position: relative;
}

.containerBox .canvas {
  height: calc(100vh - 220px);
  border: 1px solid rgb(121, 121, 121);
  background: url() repeat !important;
}

.bpp-properties-panel [type=text] {
  box-sizing: border-box;
}

.panel {
  width: 400px;
  position: absolute;
  top: 1px;
  right: 1px;
  height: calc(100vh - 220px);
  overflow: auto;
}

/* 右下角logo */
.bjs-powered-by {
  display: none;
}

.nodeSuccess:not(.djs-connection) .djs-visual > :nth-child(1) {
  stroke: #79bbff !important;
  stroke-width: 3px !important;
  fill: rgb(253, 245, 230) !important;
}

.nodeSuccess g.djs-visual > :nth-child(1) {
  stroke: #79bbff !important;
}

.djs-connection.nodeSuccess {
  path {
    stroke-width: 2px !important;
    marker-end: url(#sequenceflow-arrow-normal) !important;
  }
}

.nodeCurrent:not(.djs-connection) .djs-visual > :nth-child(1) {
  stroke: #eebe77 !important;
  stroke-width: 4px !important;
  stroke-dasharray: 20;
  stroke-dashoffset: 100;
  -webkit-transition: -webkit-transform 20s ease-out;
  transition: transform 20s ease-out;
  animation: ani 2s linear infinite;
}

@keyframes ani {
  to {
    stroke-dashoffset: 20;
  }
}

.element-overlays {
  width: 280px;
  padding: 8px;
  color: #fafafa;
  background: rgb(0 0 0 / 60%);
  border-radius: 4px;
  box-sizing: border-box;
}

.element-process-overlays {
  width: 650px;
  padding: 8px;
  color: #fafafa;
  background: rgb(0 0 0 / 60%);
  border-radius: 4px;
  box-sizing: border-box;
}

.task_info_finish {
  color: #79bbff;
}

.task_info_error {
  color: #F56C6C;
}

.task_info_wait {
  color: #E6A23C;
}

.task_info_process {
  color: #FAFCFF;
}

.task_info_success {
  color: #67C23A;
}

</style>